(*
 *  This file is part of SwfLib
 *  Copyright (c)2004 Nicolas Cannasse
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *)
open SwfTypes
(* open IO *)
(* open Printf *)
open Extjava

let push_item_length item =
    match (getMField item "length") with
        | JInt n -> n
        | _ -> assert false
(*
                        function
	| PString s -> String.length s + 1
	| PFloat _ -> 4
	| PNull -> 0
	| PUndefined -> 0
	| PReg _ -> 1
	| PBool _ -> 1
	| PDouble _ -> 8
	| PInt _ -> 4
	| PStack _ -> 1
	| PStack2 _ -> 2
*)

let push_item_id item =
    match (getMField item "id") with
        | JInt n -> n
        | _ -> assert false
(*
                    function
	| PString s -> 0
	| PFloat _ -> 1
	| PNull -> 2
	| PUndefined -> 3
	| PReg _ -> 4
	| PBool _ -> 5
	| PDouble _ -> 6
	| PInt _ -> 7
	| PStack _ -> 8
	| PStack2 _ -> 9
*)


let opcodes = Hashtbl.create 0
let opcodes_rev = Hashtbl.create 0
let opcodes_names = Hashtbl.create 0

let ( => ) code (op,name) =
	Hashtbl.add opcodes op code;
	Hashtbl.add opcodes_rev code op;
	Hashtbl.add opcodes_names op name

let short_op_codes = begin
	0x00 => (AEnd,"END");
	0x04 => (ANextFrame,"NEXTFRAME");
	0x05 => (APrevFrame,"PREVFRAME");
	0x06 => (APlay,"PLAY");
	0x07 => (AStop,"STOP");
	0x08 => (AToggleHighQuality,"TGLHIGHQULTY");
	0x09 => (AStopSounds,"STOPSOUNDS");
	0x0A => (AAddNum,"ADDNUM");
	0x0B => (ASubtract,"SUB");
	0x0C => (AMultiply,"MULT");
	0x0D => (ADivide,"DIV");
	0x0E => (ACompareNum,"CMP");
	0x0F => (AEqualNum,"EQNUM");
	0x10 => (ALogicalAnd,"LAND");
	0x11 => (ALogicalOr,"LOR");
	0x12 => (ANot,"NOT");
	0x13 => (AStringEqual,"STREQ");
	0x14 => (AStringLength,"STRLEN");
	0x15 => (ASubString,"SUBSTR");
	0x17 => (APop,"POP");
	0x18 => (AToInt,"TOINT");
	0x1C => (AEval,"EVAL");
	0x1D => (ASet,"SET");
	0x20 => (ATellTarget,"TELLTARGET");
	0x21 => (AStringAdd,"STRADD");
	0x22 => (AGetProperty,"GETPROP");
	0x23 => (ASetProperty,"SETPROP");
	0x24 => (ADuplicateMC,"DUPLICATEMC");
	0x25 => (ARemoveMC,"REMOVEMC");
	0x26 => (ATrace,"TRACE");
	0x27 => (AStartDrag,"STARTDRAG");
	0x28 => (AStopDrag,"STOPDRAG");
	0x2A => (AThrow,"THROW");
	0x2B => (ACast,"CAST");
	0x2C => (AImplements,"IMPLEMENTS");
	0x2D => (AFSCommand2,"FSCOMMAND2");
	0x30 => (ARandom,"RANDOM");
	0x31 => (AMBStringLength,"MBSTRLEN");
	0x32 => (AOrd,"ORD");
	0x33 => (AChr,"CHR");
	0x34 => (AGetTimer,"GETTIMER");
	0x35 => (AMBStringSub,"MBSTRSUB");
	0x36 => (AMBOrd,"MBORD");
	0x37 => (AMBChr,"MBCHR");
	0x3A => (ADeleteObj,"DELETEOBJ");
	0x3B => (ADelete,"DELETE");
	0x3C => (ALocalAssign,"VARSET");
	0x3D => (ACall,"CALL");
	0x3E => (AReturn,"RET");
	0x3F => (AMod,"MOD");
	0x40 => (ANew,"NEW");
	0x41 => (ALocalVar,"VAR");
	0x42 => (AInitArray,"ARRAY");
	0x43 => (AObject,"OBJECT");
	0x44 => (ATypeOf,"TYPEOF");
	0x45 => (ATargetPath,"TARGETPATH");
	0x46 => (AEnum,"ENUM");
	0x47 => (AAdd,"ADD");
	0x48 => (ACompare,"CMP");
	0x49 => (AEqual,"EQ");
	0x4A => (AToNumber,"TONUMBER");
	0x4B => (AToString,"TOSTRING");
	0x4C => (ADup,"DUP");
	0x4D => (ASwap,"SWAP");
	0x4E => (AObjGet,"OBJGET");
	0x4F => (AObjSet,"OBJSET");
	0x50 => (AIncrement,"INCR");
	0x51 => (ADecrement,"DECR");
	0x52 => (AObjCall,"OBJCALL");
	0x53 => (ANewMethod,"NEWMETHOD");
	0x54 => (AInstanceOf,"INSTANCEOF");
	0x55 => (AEnum2,"ENUM2");
	0x60 => (AAnd,"AND");
	0x61 => (AOr,"OR");
	0x62 => (AXor,"XOR");
	0x63 => (AShl,"SHL");
	0x64 => (AShr,"SHR");
	0x65 => (AAsr,"ASR");
	0x66 => (APhysEqual,"PHYSEQ");
	0x67 => (AGreater,"GT");
	0x68 => (AStringGreater,"STRGT");
	0x69 => (AExtends,"EXTENDS");
	0x9E => (ACallFrame,"CALLFRAME"); (* special case *)

end

let action_id = function
	| AGotoFrame _ -> 0x81
	| AGetURL _ -> 0x83
	| ASetReg _ -> 0x87
	| AStringPool _ -> 0x88
	| AWaitForFrame _ -> 0x8A
	| ASetTarget _ -> 0x8B
	| AGotoLabel _ -> 0x8C
	| AWaitForFrame2 _ -> 0x8D
	| AFunction2 _ -> 0x8E
	| ATry _ -> 0x8F
	| AWith _ -> 0x94
	| APush _ -> 0x96
	| AJump _ -> 0x99
	| AGetURL2 _ -> 0x9A
	| AFunction _ -> 0x9B
	| ACondJump _ -> 0x9D
	| AGotoFrame2 _ -> 0x9F
	| AUnknown (id,_) -> id

	| op ->
		try
			Hashtbl.find opcodes op
		with
			Not_found -> error "Unknown opcode id"

let action_data_length = function
	| AGotoFrame _ ->
		2
	| AGetURL (url,target) ->
		2 + String.length url + String.length target
	| ASetReg _ ->
		1
	| AStringPool strs ->
                listFoldLeft (fun acc item -> acc + String.length item) 2 strs
		(* List.fold_left (fun acc item -> acc + 1 + String.length item) 2 strs *)
	| AWaitForFrame _ ->
		3
	| AFunction2 f ->
                let f2Name = asString (getMField (JFunc2Decl f) "f2_name") in
		let base = ((String.length f2Name) + 1 + 2 + 1 + 2 + 2) in
                let f2Args = asStringList (getMField (JFunc2Decl f) "f2_name") in
                listFoldLeft (fun acc (_,s) -> acc + 2+ String.length s) base f2Args
		(* List.fold_left (fun acc (_,s) -> acc + 2 + String.length s) base f.f2_args *)
	| ASetTarget target ->
		String.length target + 1
	| AGotoLabel label ->
		String.length label + 1
	| AWaitForFrame2 _ ->
		1
	| ATry t ->
                let a = match (getTryStyleType t) with
                    | TryVariable n -> String.length n + 1
                    | TryRegister _ -> 1
                in
		1 + 6 + a
	| AWith _ ->
		2 (* the string does not count in length *)
	| APush items ->

                listFoldLeft (fun acc item -> acc + push_item_length item) 0 items
		(* List.fold_left (fun acc item -> acc + 1 + push_item_length item) 0 items *)
	| AJump _ ->
		2
	| AGetURL2 _ ->
		1
	| AFunction f ->
                (* create object function in extjava *)
                listFoldLeft (fun acc s -> acc + String.length s) 4 (newList [])
		(* List.fold_left (fun acc s -> acc + 1 + String.length s) 4 (f.f_name :: f.f_args) *)
	| ACondJump _ ->
		2
	| AGotoFrame2 (_,id) ->
		1 + (if id = None then 0 else 2)
	| AUnknown (_,data) ->
		String.length data
	| _ ->
		0

let action_length a = 
	let len = (if action_id a >= 0x80 then 3 else 1) in
	len + action_data_length a

let actions_length acts =
        listFoldLeft (fun acc a -> acc + action_length a) (action_length AEnd) acts

let jump_index_to_size acts curindex target =
	let size = ref 0 in
	if target >= 0 then begin
		for i = 1 to target do
                    let curAct = asAction (listGet acts (curindex + i)) in
                    size := !size + action_length (asActionType curAct);
		done;
	end else begin
		for i = 0 downto target+1 do
                    let curAct = asAction (listGet acts (curindex + i)) in
                    size := !size - action_length (asActionType curAct);
		done;
	end;
	!size

let f2_flags_value flags =
	let fval = function
		| ThisRegister -> 1
		| ThisNoVar -> 2
		| ArgumentsRegister -> 4
		| ArgumentsNoVar -> 8
		| SuperRegister -> 16
		| SuperNoVar -> 32
		| RootRegister -> 64
		| ParentRegister -> 128
		| GlobalRegister -> 256
	in
        listFoldLeft (fun n f -> n lor (fval f)) 0 flags
	(* List.fold_left (fun n f -> n lor (fval f)) 0 flags *)

let sprintf = Printf.sprintf

let action_string get_ident pos = function
	| AGotoFrame n -> sprintf "GOTOFRAME %d" n
	| AGetURL (a,b) -> sprintf "GETURL '%s' '%s'" a b
	| ASetReg n -> sprintf "SETREG %d" n
	| AStringPool strlist ->
		let b = Buffer.create 0 in
		Buffer.add_string b "STRINGS";
		let p = ref 0 in
                for i=0 to listLength strlist do
                    Buffer.add_string b (string_of_int !p);
                    incr p;
                    Buffer.add_char b ':';
                    let s = asString (listGet strlist i) in
                    Buffer.add_string b s;
                    Buffer.add_char b ' ';
                done;
		Buffer.contents b
	| AWaitForFrame (i,j) -> sprintf "WAITFORFRAME %d %d" i j
	| ASetTarget s -> sprintf "SETTARGET %s" s
	| AGotoLabel s -> sprintf "GOTOLABEL %s" s
	| AWaitForFrame2 n -> sprintf "WAITFORFRAME2 %d" n
	| AFunction2 f ->
		let b = Buffer.create 0 in
		Buffer.add_string b "FUNCTION2 ";
                let f2Name = asString (getMField (JFunc2Decl f) "f2_name") in
		Buffer.add_string b f2Name;
		Buffer.add_char b '(';
                let f2Args = asStringList (getMField (JFunc2Decl f) "f2_args") in
                let f2ArgsStr =
                    let resList = ref [] in
                    for i=0 to (listLength f2Args) do
                        let curItem = asString (listGet f2Args i) in
                        resList := (sprintf "%d:%s" i curItem) :: !resList;
                    done;
                    !resList
                in
		Buffer.add_string b (String.concat "," f2ArgsStr);
		Buffer.add_char b ')';

                let f2Nregs = asInt (getMField (JFunc2Decl f) "f2_nregs") in
                let f2Flags = asFlagsList (getMField (JFunc2Decl f) "f2_flags") in
		Buffer.add_string b (sprintf " nregs:%d flags:%d " f2Nregs (f2_flags_value f2Flags));

                let f2CodeLen = asInt (getMField (JFunc2Decl f) "f2_codelen") in
		Buffer.add_string b (sprintf "0x%.4X" (pos + 1 + f2CodeLen));
		Buffer.contents b
	| APush pl ->
		let b = Buffer.create 0 in
		Buffer.add_string b "PUSH";
                for i=0 to (listLength pl) do
                    match (asPushItemType (asPushItem (listGet pl i))) with
			| PString s ->
				Buffer.add_char b '"';
				Buffer.add_string b s;
				Buffer.add_char b '"'
			| PFloat _ ->
				Buffer.add_string b "<float>"
			| PNull ->
				Buffer.add_string b "null"
			| PUndefined ->
				Buffer.add_string b "undefined"
			| PReg n ->
				Buffer.add_string b (sprintf "reg:%d" n)
			| PBool fl ->
				Buffer.add_string b (if fl then "true" else "false")
			| PDouble _ ->
				Buffer.add_string b "<double>"
			| PInt i ->
				Buffer.add_string b (Int32.to_string i)
			| PStack n
			| PStack2 n ->
				Buffer.add_char b '[';
				Buffer.add_string b (string_of_int n);
				Buffer.add_char b ':';
				Buffer.add_string b (get_ident n);
				Buffer.add_char b ']';
                done;
		Buffer.contents b
	| ATry _ -> sprintf "TRY"
	| AWith n -> sprintf "WITH %d" n
	| AJump n -> sprintf "JUMP 0x%.4X" (n + pos + 1)
	| AGetURL2 n -> sprintf "GETURL2 %d" n
	| AFunction f ->
		let b = Buffer.create 0 in
		Buffer.add_string b "FUNCTION ";
                let fName = asString (getMField (JFuncDecl f) "f_name") in
		Buffer.add_string b fName;
		Buffer.add_char b '(';
                let fArgs = asStringList (getMField (JFuncDecl f) "f_args") in
                let fArgsStr =
                    let resList = ref [] in
                    for i=0 to (listLength fArgs) do
                        let curItem = asString (listGet fArgs i) in
                        resList := (sprintf "%d:%s" i curItem) :: !resList;
                    done;
                    !resList
                in
		Buffer.add_string b (String.concat "," fArgsStr);
		Buffer.add_char b ')';
                let fCodeLen = asInt (getMField (JFuncDecl f) "f_codelen") in
		Buffer.add_string b (sprintf " 0x%.4X" (pos + 1 + fCodeLen));
		Buffer.contents b
	| ACondJump n -> sprintf "CJMP 0x%.4X" (n + pos + 1)
	| AGotoFrame2 (b,None) -> sprintf "GOTOFRAME2 %b" b
	| AGotoFrame2 (b,Some i) -> sprintf "GOTOFRAME2 %b %d" b i
	| AUnknown (tag,_) -> sprintf "??? 0x%.2X" tag
	| op ->
		try
			Hashtbl.find opcodes_names op
		with
			Not_found -> assert false
